#include <user.h>
#include <ts7200.h>
#include <defs.h>
#include <vic.h>
#include <uart.h>
#include <iobuf.h>
#include <isr.h>
#include <terminal.h>
#include <heap.h>

void TerminalRxNotifier() {
  int i, c;
  char msg[IO_BUF_SIZE];
  int msglen;
  uart *const uart2 = (uart*)( UART2_BASE );
  iobuf buf;
  IobufInit(&buf);
  
  int server_tid = WhoIs("terminal-rx-server");
  
  for ( ; ; ) {
    int int_flags = AwaitEvent(EVENT_UART2_RX, NULL, 0);
    if (IobufFull(&buf))
      Panic("TerminalRxNotifier", "IO buffer full");
    if (int_flags & RIS_MASK) {
      // read 8 bytes from buffer
      for (i = 0; i < 8; i++) {
        c = uart2->data;
        IobufEnqueue(&buf, c);
      }
    } else if (int_flags & RTIS_MASK) {
      // read from FIFO until empty
      while (!(uart2->flag & RXFE_MASK)) {
        c = uart2->data;
        IobufEnqueue(&buf, c);
      }
    }

    // send data to server
    msglen = IobufSize( &buf );
    for (i = 0; i < msglen; i++) {
      IobufDequeue(&buf, &c);
      msg[i] = c;
    }
    Send( server_tid, msg, msglen, NULL, 0 );
  }
}

void TerminalRxServer() {  
  int i, i_min;
  iobuf buf;
  IobufInit(&buf);

  // Getc() queue
  iobuf request_queue;
  IobufInit(&request_queue);

  char msg[IO_BUF_SIZE];
  int msglen;
  rx_reply reply;

  int notifier_tid = Create( 7, &TerminalRxNotifier, "terminal-rx-notifier" );
  Execute(notifier_tid);  

  int sender_tid;
  uart *const uart2 = (uart*)( UART2_BASE );
  for ( ; ; ) {
    msglen = Receive(&sender_tid, msg, IO_BUF_SIZE);
    if (sender_tid == notifier_tid) {
      // notifier has data for me
      if (IobufFull(&buf))
        Panic("TerminalRxServer", "IO buffer full");
      for (i = 0; i < msglen; i++) {
        IobufEnqueue( &buf, msg[i] );
      }
      Reply(notifier_tid, NULL, 0);
      if (!IobufEmpty(&request_queue)) {
        IobufDequeue(&request_queue, &i_min);
        IobufDequeue(&buf, &(reply.ch));
        Reply( i_min, (char*)&reply, sizeof(rx_reply) );
      }
    } else {
      IobufEnqueue(&request_queue, sender_tid);
      if (!IobufEmpty(&buf)) {
        IobufDequeue(&request_queue, &i_min);
        IobufDequeue(&buf, &(reply.ch));
        Reply( i_min, (char*)&reply, sizeof(rx_reply) );
      }
    }
    uart2->ctrl |= (RIEN_MASK | RTIEN_MASK);
  }
}

// UART2 Transmit tasks
void TerminalTxNotifier() {
  int int_flags;
  
  int comserver_tid = WhoIs("terminal-com-server");
  
  for ( ; ; ) {
    int_flags = AwaitEvent( EVENT_UART2_TX, NULL, 0 );
    if (int_flags & TIS_MASK) {
      Send(comserver_tid, NULL, 0, NULL, 0);
    }
  }
  Exit();
}

void TerminalComServer() {
  int txmitter_ready = FALSE;
  int txmitter_waiting = FALSE;

  int notifier_tid = Create(8, &TerminalTxNotifier, "terminal-tx-notifier");
  Execute(notifier_tid);
  
  int txmitter_tid = WhoIs("terminal-txmitter");

  int sender_tid;
  for ( ; ; ) {
    Receive(&sender_tid, NULL, 0);
    if (sender_tid == notifier_tid) {
      // TX interrupt occurred
      Reply( notifier_tid, NULL, 0 );
      txmitter_ready = TRUE;
      if (txmitter_waiting) {
        txmitter_ready = FALSE;
        txmitter_waiting = FALSE;
        Reply( txmitter_tid, NULL, 0 );
      }
    } else if (sender_tid == txmitter_tid) {
      txmitter_waiting = TRUE;
      if (txmitter_ready) {
        txmitter_ready = FALSE;
        txmitter_waiting = FALSE;
        Reply( txmitter_tid, NULL, 0 );
      } 
    }
  }
}

void TerminalTransmitter() {
  int i, c;
  iobuf buf;
  IobufInit(&buf);
  char reply[IO_BUF_SIZE];
  int replylen;

  uart *const uart2 = (uart*)( UART2_BASE );

  int comserver_tid = Create(5, &TerminalComServer, "terminal-com-server");
  Execute(comserver_tid);

  int server_tid = WhoIs("terminal-tx-server");

  for ( ; ; ) {
    if (!IobufEmpty(&buf)) {
      // wait on TX interrupt
      Send(comserver_tid, NULL, 0, NULL, 0);
      if (IobufSize(&buf) <= 8) {
        while (!IobufEmpty( &buf )) {
          IobufDequeue( &buf, &c );
          uart2->data = c;
        }
      } else {
        for (i = 0; i < 8; i++) {
          IobufDequeue( &buf, &c );
          uart2->data = c;
        }
        uart2->ctrl |= TIEN_MASK;
      }
    } else {
      // request data from main server
      if (IobufFull(&buf))
        Panic("TerminalRxServer", "IO buffer full");
      replylen = Send(server_tid, NULL, 0, reply, IO_BUF_SIZE);
      for (i = 0; i < replylen; i++) {
        c = reply[i];
        IobufEnqueue(&buf, c);
      }
      
      // bytes arrive to transmit
      if (uart2->flag & TXBUSY_MASK) {
        uart2->ctrl |= TIEN_MASK;
      } else {
        if (IobufSize(&buf) <= 16) {
          while (!IobufEmpty( &buf )) {
            IobufDequeue( &buf, &c );
            uart2->data = c;
          }
        } else {
          for (i = 0; i < 16; i++) {
            IobufDequeue( &buf, &c );
            uart2->data = c;
          }
          uart2->ctrl |= TIEN_MASK;
        }
      }
    }
  }
  Exit();
}

void TerminalTxServer() {  
  int i, c;
  tx_req msg;
  char reply[IO_BUF_SIZE];
  int replylen;
  int txmitter_ready = FALSE;     // has \0 been sent?
  int txmitter_waiting = FALSE;   // is the transmitter waiting?
  iobuf buf;
  IobufInit(&buf);
  
  int txmitter_tid = Create( 8, &TerminalTransmitter, "terminal-txmitter" );
  Execute(txmitter_tid);

  int sender_tid;
  for ( ; ; ) {
    Receive( &sender_tid, (char*)&msg, sizeof(tx_req));
    if (sender_tid == txmitter_tid) {
      // transmitter data request received!
      if (txmitter_ready && !IobufEmpty(&buf)) {
        replylen = IobufSize( &buf );
        for (i = 0; i < replylen; i++) {
          IobufDequeue( &buf, &c );
          reply[i] = c;
        }
        txmitter_ready = FALSE;
        txmitter_waiting = FALSE;
        Reply( txmitter_tid, reply, replylen );
      } else {
        // no data available, so block it for now
        txmitter_waiting = TRUE;
      }
    } else {
      // system call request received!
      if (IobufFull(&buf))
        Panic("TerminalTxServer", "IO buffer full");
      Reply(sender_tid, NULL, 0);
      
      char *str = msg.str;
      for (i = 0; i < msg.len; i++) {
          IobufEnqueue(&buf, *str++);
      }
      txmitter_ready = TRUE;
      
      // if the transmitter is waiting, we respond to it here
      if (txmitter_ready && txmitter_waiting) {
        replylen = IobufSize( &buf );
        for (i = 0; i < replylen; i++) {
          IobufDequeue( &buf, &c );
          reply[i] = c;
        }
        txmitter_ready = FALSE;
        txmitter_waiting = FALSE;
        Reply( txmitter_tid, reply, replylen );
      }
    }
  }
}
